Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Enqueue only required frontend styles - V1 #6166

Closed
wants to merge 41 commits into from
Closed

[WIP] Enqueue only required frontend styles - V1 #6166

wants to merge 41 commits into from

Conversation

kanishkdudeja
Copy link

@kanishkdudeja kanishkdudeja commented Apr 13, 2018

This PR is a proof of concept for #5445. It only enqueues front-end styles for block types present in the current page/post.

Dependencies

Version 1 (enqueues styles inside <body>)

This branch hooks a filter on the_content to get block types present in the page while stripping block comments from the HTML.

Since filters hooked to the_content are executed much later in the lifecycle (after actions hooked to wp_enqueue_scripts are executed), the stylesheets are being enqueued in the <body> section.

Version 2 (enqueues styles inside <head>)

#6200 reads the current post's content on the wp_enqueue_scripts action (much earlier than the_content). Therefore, that enables us to be able to enqueue required styles inside the <head> section.

An issue with that approach is that if a plugin adds a core block using a filter on the_content later, our code won't be able to enqueue styles for that block type since we would have read the post's content much earlier. Maybe we can try re-parsing the post's content to enqueue styles for any remaining block types after all filters on the_content have executed.

To Do

  • Make this work for dynamic blocks
  • Bundling for styles (Right now, styles for each block type are enqueued separately)
  • Provide a way to disable this functionality (meaning, enqueue all styles regardless of block types present in the page)
  • Provide a way to extend this functionality

Kanishk Dudeja added 15 commits April 8, 2018 21:27
Added a class "WP_Parsed_Block_Types_Registry" to manage the storage of block types parsed from the HTML (while stripping block comments)
Core block types parsed from the HTML may not contain 'core/'. Added a function gutenberg_prefix_core_namespace_if_not_found() to do the same.
This is work in progress and will be completely overhauled.

Just committing it so that I don't lose it.
…e enqueued

The new logic checks if the current requested web page is in the frontend. And if it's page or a post.

If the answer is yes to both, it only enqueues styles for blocks which are present in the post/page.
@diegoliv
Copy link

Hey @kanishkdudeja I checked your work on this PR and on #5445 and I think that my goal on #5740 might be aligned with what you're doing here. Long story short, I think we could leverage the framework you're working for bundling frontend assets for blocks and also allow devs to add attribute based styles to be bundled into the final static css file that will be generated. What do you think?

@gziolo gziolo added [Feature] Extensibility The ability to extend blocks or the editing experience Framework Issues related to broader framework topics, especially as it relates to javascript labels Apr 13, 2018
lib/blocks.php Outdated
* @return string Name of the block type, prefixed by the core namespace if needed.
*/
function gutenberg_prefix_core_namespace_if_not_found( $block_type ) {
$block_type = trim( $block_type );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we expect leading or trailing whitespace?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will not expect leading/trailing whitespace from where the function is being called right now (gutenberg_process_block_comment()).

But this function can also be called as a library function from other functions in future. So, I thought it might be a good idea to trim the input since a normalized block type cannot contain leading/trailing whitespace.

What do you think @aduth ?

lib/blocks.php Outdated
* @param string $block_type Name of the block type.
* @return string Name of the block type, prefixed by the core namespace if needed.
*/
function gutenberg_prefix_core_namespace_if_not_found( $block_type ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could simplify / generalize the function name to reflect the fact that we want a normal/standard block type to work with:

gutenberg_normalize_block_type

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I also thought about this. Couldn't think of a proper name then.

Have renamed it now.

lib/blocks.php Outdated
// Only process the block comment if it's not a closing tag for a block. If it's a closing tag, we can just return.
if ( preg_match( '/\/wp:/m', $block_comment ) !== 1 ) {

$match = Array();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to explicitly create a variable reference for preg_match to assign into. This line could be removed altogether and still work just as well.

Aside: I'd encourage lowercase array(). Confused me for a moment as I'm not used to seeing it this way, I thought it was some class special constructor.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Removed the line altogether.

lib/blocks.php Outdated

$match = Array();

preg_match( '/wp:(.*?)\s+/m', $block_comment, $match);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coding standards: Space before closing parenthesis.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. This has been resolved.

lib/blocks.php Outdated
* and so on.
* @return string Returns an empty string to preg_replace_callback() for each of the block comments
*/
function gutenberg_process_block_comment( $matches ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an aside, might be worth tracking conversation at #5967, since at some point we may want to re-introduce a grammar for block rendering which doesn't rely as much on regular expressions. I think this is fine given the current implementation though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'll be tracking the conversation at #5967 now :)

$is_editor = ( 'enqueue_block_editor_assets' === current_action() );
$is_frontend = ! $is_editor;

$is_post_or_page = is_single() || is_page();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we just want to use is_singular here? Or are we intentionally excluding attachment type?

https://developer.wordpress.org/reference/functions/is_singular/

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original intention was to exclude attachment type. But it turns out is_single() returns true for attachment types as well.

Thanks for pointing this out. Have replaced this with: is_singular( array( 'post', 'page') )

@@ -770,10 +780,52 @@ function gutenberg_enqueue_registered_block_scripts_and_styles() {
wp_enqueue_script( $block_type->editor_script );
}
}

if ( $enqueue_only_required_styles ) {
add_action( 'the_content', 'enqueue_required_frontend_block_styles', 10 );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Priority 10 is default, so can be omitted altogether as an argument.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd mentioned wanting to support other plugins which modify the_content to revise blocks. Does that mean enqueueing needs to occur at a later priority (higher value)? What about the parsing to determine which blocks are present in content? Similarly later? Currently at 9, if a plugin would modify content at the default priority, I don't think WP_Parsed_Block_Types_Registry would reflect those modifications.

Copy link
Author

@kanishkdudeja kanishkdudeja Apr 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Thanks for pointing this out. I'm sorry this completely skipped my mind.

Now, gutenberg_process_block_comments runs with PHP_INT_MAX - 1 priority and enqueue_required_frontend_block_styles runs with PHP_INT_MAX priority since front-end styles can be enqueued once block types are parsed and saved in the registry.

if ( ! isset( $block_type ) ) {
// Log the error here
} else {
if ( isset( $block_type->style ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collapse into elseif ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. This has been resolved.

$block_type = $all_block_types_registry->get_registered( $block_type_name );

if ( ! isset( $block_type ) ) {
// Log the error here
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you propose to log it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have logged it using trigger_error in the latest commit.

This function will do the following things if WP_DEBUG is set as true:

  • Print it in wp-content/debug.log file (if configured using WP_DEBUG_LOG).
  • Will show it as a notice on the post.

If WP_DEBUG is set as false (by default), it won't print the log anywhere.

// Log the error here
} else {
if ( isset( $block_type->style ) ) {
wp_enqueue_style( $block_type->style );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So because we enqueue in the_content, when exactly does the script get output? As part of late style printing in wp_footer? Would that trigger a flash of unstyled content for the block since the markup of the block is output before its stylesheet? If so, could we overcome this by forcing the output of stylesheets right here, before the content (echo immediately) ?

@aduth
Copy link
Member

aduth commented Apr 13, 2018

Broad thoughts I have are:

  • Maybe the registry is overkill, I know we’re mimicking the block registry, but it’s not clear why
  • Tying ourselves a bit closer to parsing content with regular expressions + string manipulation
  • Will we have issues with ordering of style enqueues + plugin filters

Kanishk Dudeja added 6 commits April 14, 2018 14:37
Renamed gutenberg_prefix_core_namespace_if_not_found() to gutenberg_normalize_block_type()
Removed '$match = Array()' since we don't need  to declare a variable for preg_match()
Fixed syntax to suit coding standards
…blocks

Our rendering algorithm for dynamic blocks was stripping block comments after rendering those dynamic blocks.

Disabled that functionality since block comments are needed to find out block types present in the page later.
Kanishk Dudeja added 7 commits April 15, 2018 22:57
get_last_error() function in Block Type Validator was referencing $errors as a local variable, whereas it should have referenced it as a class variable.

Have fixed it in this commit.
Removed _doing_it_wrong notices from Block Type Validator class since the same are being called in the caller
Kanishk Dudeja added 6 commits April 16, 2018 17:04
…ts and enqueueing frontend styles

Earlier, functions for stripping block comments and enqueueing frontend styles were given 9 and 10 priority respectively.

This didn't work well since plugins might append more blocks to content (through filters at higher priority).

Therefore, have assigned PHP_INT_MAX - 1 and PHP_INT_MAX priority to these so that these can be run as late as possible
@kanishkdudeja
Copy link
Author

Thanks for the elaborate and critical code review @aduth.

Maybe the registry is overkill, I know we’re mimicking the block registry, but it’s not clear why

The WP_Parsed_Block_Registry serves 2 purposes here:

  • Maintains some global state. So that functions which enqueue frontend styles for required block types can get a list of parsed block types after they are stripped from the HTML.
  • The same class can also be used by plugins later for any custom needs. If a plugin needs to get a list of block types parsed from the current post/page, they can use the same class.

Tying ourselves a bit closer to parsing content with regular expressions + string manipulation

Yes, I felt the same. But we're already using similar regular expressions to parse dynamic block patterns to be able to render them on the server-side. So, I thought this should be okay for now.

As you suggested, I'm following the conversation on #5967 but do let me know if you feel we should use the PEG Parser to be able to parse block types while stripping block comments from the code.

Will we have issues with ordering of style enqueues + plugin filters

I don't think we will. That is a possibility in #6200 (V2).

Since we are enqueuing required styles in the <body> section, parsing block types (while stripping block comments) is done through a filter (with priority PHP_INT_MAX) hooked to the_content. Therefore, unless plugins attach filters with the same priority (maximum possible), we should be able to parse all block types (even those added by plugins) before we start to enqueue stylesheets for those block types.

Kanishk Dudeja added 7 commits April 17, 2018 00:50
A comment describing the priority for a filter has now been moved to just above the add_filter() function so that it's more intuitive while reading.
Earlier, we were parsing block types from the HTML and saving them to the registry even for pages like category / archive pages (which contain multiple posts).

Have disabled this functionality for such pages now since the registry is only needed for intelligent enqueueing of assets (which is needed for single post / page types as of now).
@gziolo gziolo mentioned this pull request May 14, 2018
@gziolo gziolo added the [Type] Technical Prototype Offers a technical exploration into an idea as an example of what's possible label Jul 10, 2018
@aduth
Copy link
Member

aduth commented Sep 13, 2018

This pull request appears to have languished and will not be easily reconciled with master. Please feel free to reopen and rebase against the current master, or open a new pull request.

@aduth aduth closed this Sep 13, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Extensibility The ability to extend blocks or the editing experience Framework Issues related to broader framework topics, especially as it relates to javascript [Type] Technical Prototype Offers a technical exploration into an idea as an example of what's possible
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants